Skip to content

RHINENG-26529: Revert RHINENG-25147 workspace changes (v3.8.260) #2203

Merged
MichaelMraka merged 2 commits into
RedHatInsights:masterfrom
MichaelMraka:pr1
May 21, 2026
Merged

RHINENG-26529: Revert RHINENG-25147 workspace changes (v3.8.260) #2203
MichaelMraka merged 2 commits into
RedHatInsights:masterfrom
MichaelMraka:pr1

Conversation

@MichaelMraka
Copy link
Copy Markdown
Collaborator

@MichaelMraka MichaelMraka commented May 20, 2026

Secure Coding Practices Checklist GitHub Link

Secure Coding Checklist

  • Input Validation
  • Output Encoding
  • Authentication and Password Management
  • Session Management
  • Access Control
  • Cryptographic Practices
  • Error Handling and Logging
  • Data Protection
  • Communication Security
  • System Configuration
  • Database Security
  • File Management
  • Memory Management
  • General Coding Practices

Summary by Sourcery

Reintroduce JSONB-based workspaces on systems and wire inventory group filtering through the stack instead of workspace IDs.

Enhancements:

  • Replace workspace_id/workspace_name columns with a JSONB workspaces column on system_inventory and update the schema, migrations, and models accordingly.
  • Propagate inventory group filters (grouped/ungrouped) throughout database helpers, middleware, and controllers in place of workspace ID lists, including Kessel and RBAC integrations.
  • Adjust system, advisory, package, template, and tag queries plus CSV/JSON exports to use the new workspaces representation and group-based filtering.
  • Add a helper type for inventory groups to support JSONB storage and scanning, and refine group-name filtering logic to work on workspaces JSONB.

Build:

  • Introduce a new migration to backfill the workspaces JSONB column from legacy workspace_id/workspace_name when present and drop the old columns.

Tests:

  • Update database fixtures and unit tests across database, middleware, listener, and controller packages to cover the new workspaces JSONB model and group-based access rules.

@MichaelMraka MichaelMraka requested a review from a team as a code owner May 20, 2026 15:42
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 20, 2026

Reviewer's Guide

Reverts the previous "simplify workspaces" change by restoring system_inventory.workspaces JSONB semantics end‑to‑end: database schema and migrations, test data, GORM models, listeners, middleware, and controllers now read/write inventory groups via JSONB workspaces and a Groups helper type, and all workspace-based filters are rewritten to use inventory group maps and JSONB containment rather than scalar workspace_id/name fields.

File-Level Changes

Change Details Files
Test data and schema now model workspaces as JSONB inventory groups instead of scalar workspace_id/workspace_name columns.
  • dev/test_data.sql updated to populate system_inventory.workspaces with JSON arrays of inventory group objects and to adjust group counts used by tests.
  • database_admin/schema/create_schema.sql changes system_inventory definition to remove workspace_id/workspace_name, add workspaces JSONB, and create a GIN index on workspaces.
  • Adds migration 154 to backfill workspaces from existing workspace_id/workspace_name if present and then drop those columns; migration 153 is turned into a noop and schema version is bumped to 154.
dev/test_data.sql
database_admin/schema/create_schema.sql
database_admin/migrations/153_simplify_workspaces.up.sql
database_admin/migrations/154_simplify_workspaces.up.sql
Database helpers and filtering logic are refactored from workspace ID slices to inventory group maps using JSONB containment.
  • Systems, SystemAdvisories, SystemPackages, and SystemAdvisoriesByInventoryID in base/database/utils.go now accept map[string]string groups instead of []string workspace IDs and pass this into ApplyInventoryWorkspaceFilter.
  • ApplyInventoryWorkspaceFilter now interprets grouped/ungrouped keys, applying workspaces @> ANY (?::jsonb[]) and special handling for ungrouped systems (workspaces = '[]') instead of workspace_id IN (?).
  • Controller-side inventory filters using group_name build JSONB fragments via utils.ParseInventoryGroup and query si.workspaces @> ANY (?::jsonb[]).
  • Associated tests in base/database/utils_test.go and controller tests are updated to construct and assert against the new groups map and workspaces JSON semantics.
base/database/utils.go
base/database/utils_test.go
manager/controllers/utils.go
manager/controllers/utils_test.go
Listener and model layer now persist and expose full inventory group lists via a JSONB workspaces column and new inventory.Groups type.
  • Introduces inventory.Groups (slice of Group) implementing driver.Valuer/sql.Scanner to store/load JSONB workspaces.
  • base/models.SystemInventory replaces WorkspaceID/WorkspaceName with Workspaces *inventory.Groups.
  • listener/updateSystemPlatform drops UUID-based workspace resolution and instead converts host.Groups directly to inventory.Groups and writes them to Workspaces.
  • Listener tests (common_test.go, upload_test.go) are updated to assert inv.Workspaces against host.Groups instead of scalar workspace fields and to adjust test host group IDs.
base/inventory/inventory.go
base/models/models.go
listener/upload.go
listener/common_test.go
listener/upload_test.go
RBAC and Kessel middleware now compute and propagate inventory group selections rather than workspace ID lists.
  • manager/middlewares/rbac.go now stores groups in utils.KeyInventoryGroups and uses utils.KeyGrouped/utils.KeyUngrouped when building the group map; constants are moved to base/utils/gin.go.
  • New processWorkspaces in manager/middlewares/kessel.go converts Kessel workspace resource IDs to JSON-encoded inventory group objects via utils.ParseInventoryGroup and returns the grouped map; hasPermissionKessel sets KeyInventoryGroups instead of KeyInventoryWorkspaces.
  • Kessel and RBAC tests are updated to assert the new group map format rather than workspace ID slices.
manager/middlewares/rbac.go
manager/middlewares/rbac_test.go
manager/middlewares/kessel.go
manager/middlewares/kessel_test.go
base/utils/gin.go
All controllers and exports are adjusted to consume inventoryGroups from context and to use workspaces-based queries and response shapes.
  • Handlers across systems, advisories, packages, templates, template systems updates/deletes, system advisories view, system tags, and export endpoints now read c.GetStringMapString(utils.KeyInventoryGroups) and pass the groups map into database helpers instead of workspace ID slices.
  • Query builders (buildAdvisoryAccountDataQuery, buildQueryAdvisoriesTagged, packageSystemsQuery, packageVersionsQuery, systemPackageQuery, templatesQuery, etc.) are updated to accept and pass through groups maps.
  • System and related response DTOs drop explicit SystemWorkspace (workspace_id/name) fields; CSV headers and expectations in export tests are updated to remove workspace_id/name and to emit only groups JSON with the new inventory-group IDs.
  • Gintesting helpers stop injecting KeyInventoryWorkspaces into test contexts, relying instead on controller-specific setup.
manager/controllers/systems.go
manager/controllers/systems_export.go
manager/controllers/systems_export_test.go
manager/controllers/system_detail.go
manager/controllers/system_packages.go
manager/controllers/system_packages_export.go
manager/controllers/system_advisories.go
manager/controllers/system_advisories_export.go
manager/controllers/advisories.go
manager/controllers/advisories_export.go
manager/controllers/advisory_systems.go
manager/controllers/advisory_systems_export.go
manager/controllers/packages.go
manager/controllers/packages_export.go
manager/controllers/package_versions.go
manager/controllers/package_systems.go
manager/controllers/package_systems_export.go
manager/controllers/systemtags.go
manager/controllers/template_systems.go
manager/controllers/template_systems_update.go
manager/controllers/template_systems_delete.go
manager/controllers/template_subscribed_systems_update.go
manager/controllers/template_systems_export.go
manager/controllers/template_systems_export_test.go
manager/controllers/advisory_systems_export_test.go
manager/controllers/package_systems_export_test.go
base/core/gintesting.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In ApplyInventoryWorkspaceFilter you build the subquery using the global DB handle instead of the passed-in tx, which can unintentionally bypass transaction/replica routing and is safer to base off tx (e.g., sub := tx.Session(&gorm.Session{NewDB:true}).Where(...)).
  • There is now a second path (processWorkspaces) that constructs the grouped-inventory JSON using ParseInventoryGroup, largely duplicating the behavior already used in the RBAC middleware; consider centralizing this group-building logic to avoid future drift in how the JSONB array is formatted.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `ApplyInventoryWorkspaceFilter` you build the subquery using the global `DB` handle instead of the passed-in `tx`, which can unintentionally bypass transaction/replica routing and is safer to base off `tx` (e.g., `sub := tx.Session(&gorm.Session{NewDB:true}).Where(...)`).
- There is now a second path (`processWorkspaces`) that constructs the grouped-inventory JSON using `ParseInventoryGroup`, largely duplicating the behavior already used in the RBAC middleware; consider centralizing this group-building logic to avoid future drift in how the JSONB array is formatted.

## Individual Comments

### Comment 1
<location path="base/database/utils.go" line_range="243" />
<code_context>
-func ApplyInventoryWorkspaceFilter(tx *gorm.DB, workspaceIDs []string) *gorm.DB {
-	if len(workspaceIDs) == 0 {
-		utils.LogWarn("there should always be some workspaces, at least root workspace")
+func ApplyInventoryWorkspaceFilter(tx *gorm.DB, groups map[string]string) *gorm.DB {
+	if _, ok := groups[utils.KeyGrouped]; !ok {
+		if _, ok := groups[utils.KeyUngrouped]; ok {
</code_context>
<issue_to_address>
**issue (complexity):** Consider replacing the map-based grouping API with a typed filter struct and using only the local `tx` to build the workspace query to make the code more explicit and type-safe.

You can keep the new grouping behavior but simplify the API and query composition to reduce indirection and reliance on magic keys.

### 1. Replace `map[string]string` with a typed struct

Instead of passing `map[string]string` and depending on `utils.KeyGrouped` / `utils.KeyUngrouped`, introduce a small struct and update the call sites. This keeps behavior but makes usage explicit and type-safe.

```go
// new type
type InventoryGroupFilter struct {
    Grouped       []string
    IncludeUngrouped bool
}
```

Update signatures:

```go
func Systems(tx *gorm.DB, accountID int, gf InventoryGroupFilter, joins ...join) *gorm.DB {
    tx = tx.Table("system_inventory si").
        Joins("JOIN system_patch spatch ON si.id = spatch.system_id AND si.rh_account_id = spatch.rh_account_id").
        Where("si.rh_account_id = ?", accountID)
    tx = (joinsT)(joins).apply(tx)
    return ApplyInventoryWorkspaceFilter(tx, gf)
}

func SystemAdvisories(tx *gorm.DB, accountID int, gf InventoryGroupFilter, joins ...join) *gorm.DB {
    tx = Systems(tx, accountID, gf).
        Joins("JOIN system_advisories sa on sa.system_id = si.id AND sa.rh_account_id = ?", accountID)
    return (joinsT)(joins).apply(tx)
}

func SystemPackages(tx *gorm.DB, accountID int, gf InventoryGroupFilter, joins ...join) *gorm.DB {
    tx = Systems(tx, accountID, gf).
        Joins("JOIN system_package2 spkg on spkg.system_id = si.id AND spkg.rh_account_id = ?", accountID).
        Joins("JOIN package p on p.id = spkg.package_id").
        Joins("JOIN package_name pn on pn.id = spkg.name_id")
    return (joinsT)(joins).apply(tx)
}

func SystemAdvisoriesByInventoryID(
    tx *gorm.DB,
    accountID int,
    gf InventoryGroupFilter,
    inventoryID string,
    joins ...join,
) *gorm.DB {
    tx = SystemAdvisories(tx, accountID, gf).
        Where("si.inventory_id = ?::uuid", inventoryID)
    return (joinsT)(joins).apply(tx)
}
```

Where you currently build the `map[string]string`, convert to `InventoryGroupFilter` once at the boundary instead of scattering key lookups.

### 2. Simplify `ApplyInventoryWorkspaceFilter` and avoid global `DB`

Replace map-based control flow and usage of global `DB` with a straightforward, `tx`-only implementation:

```go
func ApplyInventoryWorkspaceFilter(tx *gorm.DB, gf InventoryGroupFilter) *gorm.DB {
    // no grouped filters
    if len(gf.Grouped) == 0 {
        if gf.IncludeUngrouped {
            return tx.Where("si.workspaces = '[]'")
        }
        return tx // no workspace condition
    }

    cond := tx.Where("si.workspaces @> ANY (?::jsonb[])", gf.Grouped)
    if gf.IncludeUngrouped {
        cond = cond.Or("si.workspaces = '[]'")
    }

    return tx.Where(cond)
}
```

This preserves:

- “only ungrouped” behavior when there are no grouped values but `IncludeUngrouped` is true,
- “no filter” behavior when both grouped slice is empty and ungrouped is false,
- combined grouped + ungrouped behavior when both are requested,

while removing:

- magic string keys and map presence checks,
- the subtle use of global `DB` during query composition.
</issue_to_address>

### Comment 2
<location path="manager/middlewares/kessel.go" line_range="38" />
<code_context>
 	return clientBuilder.Build()
 }

+func processWorkspaces(workspaces []*kesselv2.StreamedListObjectsResponse) (map[string]string, error) {
+	defer func(start time.Time) {
+		utils.LogDebug("durationMs", time.Since(start).Milliseconds(), "processed workspaces")
</code_context>
<issue_to_address>
**issue (complexity):** Consider replacing the stringly-typed map-based inventory group contract with a small typed filter struct and moving SQL-specific formatting into the DB layer to clarify responsibilities and types.

You can simplify this by replacing the stringly-typed `map[string]string` (with a Postgres array literal) with a small typed struct and deferring the SQL formatting to the DB layer. That keeps behavior the same but makes the contract explicit and localizes DB-specific knowledge.

For example:

```go
// utils/inventory_filter.go (or similar)
type InventoryGroupFilter struct {
    Grouped []string
    // Ungrouped []string // add if/when needed
}
```

Update `processWorkspaces` to return this type:

```go
func processWorkspaces(workspaces []*kesselv2.StreamedListObjectsResponse) (*utils.InventoryGroupFilter, error) {
    defer func(start time.Time) {
        utils.LogDebug("durationMs", time.Since(start).Milliseconds(), "processed workspaces")
    }(time.Now())

    groups := make([]string, 0, len(workspaces))
    for _, workspace := range workspaces {
        group, err := utils.ParseInventoryGroup(&workspace.Object.ResourceId, nil)
        if err != nil {
            // couldn't marshal inventory group to json
            continue
        }
        groups = append(groups, group)
    }

    if len(groups) == 0 {
        return nil, errors.New("no workspaces found")
    }

    return &utils.InventoryGroupFilter{Grouped: groups}, nil
}
```

Then store this struct directly in the context:

```go
inventoryGroups, err := processWorkspaces(workspaces)
if err != nil {
    utils.LogWarn(err.Error())
    c.AbortWithStatusJSON(http.StatusUnauthorized, utils.ErrorResponse{Error: "Missing permission"})
    return
}
c.Set(utils.KeyInventoryGroups, inventoryGroups)
```

Finally, move the Postgres-array-literal/SQL formatting into `ApplyInventoryWorkspaceFilter` (or equivalent DB layer function):

```go
func ApplyInventoryWorkspaceFilter(q sq.SelectBuilder, filter *utils.InventoryGroupFilter) sq.SelectBuilder {
    if filter == nil || len(filter.Grouped) == 0 {
        return q
    }

    // Build Postgres array literal or use pq.Array as appropriate
    groupedLiteral := fmt.Sprintf("{%s}", strings.Join(filter.Grouped, ","))
    return q.Where("inventory_group = ANY (?)", groupedLiteral)
}
```

This keeps the same behavior (same eventual SQL), but:

- Removes the cross-layer `map[string]string` contract and magic keys.
- Makes the filter’s shape visible in the type system.
- Localizes the DB-specific representation to the DB layer, avoiding leaky abstractions.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread base/database/utils.go
Comment thread manager/middlewares/kessel.go
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 20, 2026

Codecov Report

❌ Patch coverage is 76.62338% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.23%. Comparing base (3fcab0a) to head (2075163).

Files with missing lines Patch % Lines
base/inventory/inventory.go 0.00% 15 Missing ⚠️
base/database/utils.go 56.25% 7 Missing ⚠️
manager/middlewares/kessel.go 64.70% 3 Missing and 3 partials ⚠️
manager/controllers/advisories_export.go 33.33% 1 Missing and 1 partial ⚠️
manager/controllers/system_detail.go 50.00% 2 Missing ⚠️
manager/controllers/template_systems.go 71.42% 2 Missing ⚠️
manager/controllers/utils.go 77.77% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2203      +/-   ##
==========================================
+ Coverage   59.08%   59.23%   +0.14%     
==========================================
  Files         137      137              
  Lines        8775     8795      +20     
==========================================
+ Hits         5185     5210      +25     
+ Misses       3042     3037       -5     
  Partials      548      548              
Flag Coverage Δ
unittests 59.23% <76.62%> (+0.14%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MichaelMraka MichaelMraka merged commit 7e8a887 into RedHatInsights:master May 21, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants